package command

import (
	"fmt"
	"io/fs"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"

	"github.com/fatih/color"
	"gorm.io/driver/clickhouse"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

func max(m int, s string) int {
	if m < len(s) {
		return len(s)
	} else {
		return m
	}
}

func GetGOENV(name string) string {
	cmd := exec.Command("go", "env", name)
	env, _ := cmd.Output()
	cmd.Run()

	res := string(env)

	res = strings.Trim(res, "\r")
	res = strings.Trim(res, "\n")

	return res
}

func ParamError(name string) {
	color.Set(color.FgRed)
	fmt.Printf("参数【%s】错误\n", name)
	color.Set(color.FgWhite)
	os.Exit(0)
}

func ParamCheck(key, value string) {
	if value == "" {
		ParamError(key)
	}
}

func GetCurrentDir() string {
	dir, _ := os.Getwd()
	return TrimPath(filepath.Clean(dir))
}

func IsDir(p string) (bool, error) {
	f, e := os.Stat(p)
	if e != nil {
		return false, e
	}
	return f.IsDir(), nil
}

func PathExists(p string) (bool, error) {
	_, err := os.Stat(p)
	if err == nil { // 文件或者目录存在
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

func TrimPath(p string) string {
	res := filepath.Clean(p)
	res = strings.TrimRight(res, "\\")
	res = strings.TrimRight(res, "/")

	return res
}

func Exec(command string, dir string) {
	var cmd *exec.Cmd
	params := strings.Split(command, " ")
	if len(params) == 0 {
		return
	}
	if len(params) == 1 {
		cmd = exec.Command(params[0])
	} else {
		cmd = exec.Command(params[0], params[1:]...)
	}

	cmd.Dir = dir
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stdout
	err := cmd.Run()
	if err != nil {
		log.Fatalln(err)
	}
}

func HandleError(err any) {
	if err != nil {
		color.Set(color.FgRed)
		fmt.Println(err)
		fmt.Println(runtime.Caller(1))
		color.Set(color.FgWhite)
		os.Exit(0)
	}
}

func ProjectName() (string, error) {
	content, err := ioutil.ReadFile("go.mod")
	if err != nil {
		return "", err
	}

	reg := regexp.MustCompile(`^module +([^\r|\n]+)`)
	res := reg.FindAllSubmatch(content, -1)

	return string(res[0][1]), err
}

func Snake(data string) string {
	reg := regexp.MustCompile(`[A-Z]`)
	res := reg.ReplaceAllString(data, "_$0")
	res = strings.ToLower(res)
	res = strings.Trim(res, "_")

	return res
}

func AddImport(source, content string) string {
	hasImport := regexp.MustCompile(`import `).MatchString(source)
	isMulti := regexp.MustCompile(`import +\(`).MatchString(source)
	packageContent := regexp.MustCompile(`package [^\n]\n`).FindString(source)
	if !hasImport {
		source = strings.ReplaceAll(source, packageContent, fmt.Sprintf("%s\n%s", packageContent, fmt.Sprintf("import (\n\t\"%s\"\n)", content)))
	} else if isMulti {
		source = regexp.MustCompile(`import +\(`).ReplaceAllString(source, fmt.Sprintf("import (\n\t\"%s\"", content))
	} else {
		source = regexp.MustCompile(`import +\"([^\"]+)\"`).ReplaceAllString(source, fmt.Sprintf("import (\n\t\"%s\"\n\t\"$1\"\n)", content))
	}

	return source
}

func FindImport(content string) []string {
	res := regexp.MustCompile(`import \(([^\)]|[\n])*\)`).FindAllString(content, -1)

	return res
}

func FindConstant(content string) []string {
	contentByte := []byte(content)
	constantStartByte := []byte("const (")
	constantEndByte := []byte("\n)\n")
	contentByteLen := len(contentByte)
	contentStartByteLen := len(constantStartByte)
	contentEndByteLen := len(constantEndByte)

	result := []string{}
	startPos := []int{}
	endPos := []int{}

	for x := 0; x < contentByteLen; x++ {
		startY := x + contentStartByteLen
		endY := x + contentEndByteLen
		if startY < contentByteLen && string(contentByte[x:startY]) == "const (" {
			startPos = append(startPos, x)
		}
		if endY < contentByteLen && string(contentByte[x:endY]) == "\n)\n" {
			endPos = append(endPos, endY)
		}
	}

	for _, constx := range startPos {
		for _, consty := range endPos {
			if consty > constx {
				result = append(result, string(contentByte[constx:consty]))
				break
			}
		}
	}

	return result
}

func FindStructFromContent(content string) []string {
	content = strings.ReplaceAll(content, "\r\n", "\n")
	content = strings.ReplaceAll(content, "\r", "\n")
	structures := regexp.MustCompile(`type +(\w+) +struct *{ *\n`).FindAllStringSubmatch(content, -1)

	contentByte := []byte(content)
	structureEndByte := []byte("\n}\n")
	contentByteLen := len(contentByte)
	structureEndByteLen := len(structureEndByte)

	result := []string{}

	for _, structure := range structures {
		structureLen := len([]byte(structure[0]))
		structureRightPos := -1
		startPos := -1
		endPos := -1
		for i := 0; i < contentByteLen; i++ {
			j := i + structureLen
			if j < contentByteLen && string(contentByte[i:j]) == structure[0] {
				startPos = i
				structureRightPos = j
				break
			}
		}
		for i := structureRightPos; i < contentByteLen; i++ {
			j := i + structureEndByteLen
			if j < contentByteLen && string(contentByte[i:j]) == "\n}\n" {
				endPos = j
				break
			}
		}
		if startPos >= 0 && endPos >= 0 {
			result = append(result, string(contentByte[startPos:endPos]))
		}
	}

	return result
}

func GetDefaultValueToString(typ string) string {
	var result string
	switch typ {
	default:
		result = "nil"
	case "string":
		result = "\"\""
	case "bool":
		result = "false"
	case "uint8", "uint16", "uint32", "uint64", "uint", "int8", "int16", "int32", "int64", "int", "float32", "float64", "byte", "rune":
		result = "0"
	}
	return result
}

func DbTypeToGoType(dataType, columnType string) string {
	var result = "string"

	switch dataType {
	case "bigint", "int", "tinyint":
		result = "int64"
	case "datetime", "timestamp":
		result = "time.Time"
	case "float":
		result = "float32"
	}
	return result
}

func DbTypeFormat(dataType string) string {
	if strings.Contains(dataType, "string") {
		return "string"
	} else if strings.Contains(dataType, "int") {
		return "int"
	} else if strings.Contains(dataType, "float") {
		return "float"
	} else if strings.Contains(dataType, "datetime") {
		return "datetime"
	} else if strings.Contains(dataType, "timestamp") {
		return "timestamp"
	} else {
		return "string"
	}
}

func ConnectDSN(u string) (conn gorm.Dialector, db string) {
	var dsn string
	data, err := url.Parse(u)
	if err != nil {
		HandleError(err)
	}
	db = strings.Trim(data.Path, "/")
	switch data.Scheme {
	case "mysql":
		dsn = fmt.Sprintf("%s@tcp(%s)%s", data.User.String(), data.Host, data.Path)
		conn = mysql.Open(dsn)
	case "clickhouse":
		dsn = u
		conn = clickhouse.Open(dsn)
	}
	return
}

func SnakeToCamel(s string) string {
	s = strings.ToLower(s)
	split := strings.Split(s, "_")
	var result string
	for _, val := range split {
		result = fmt.Sprintf("%s%s", result, strings.Title(val))
	}

	return result
}

func Recover() {
	if r := recover(); r != nil {
		color.Set(color.FgHiRed)
		fmt.Printf("工具执行异常: %v\n\n", r)
		color.Set(color.FgHiMagenta)
		var count = 1
		for {
			_, file, line, ok := runtime.Caller(count)
			if !ok {
				break
			}
			fmt.Printf("%s:%d\n", file, line)
			count++
		}
		color.Set(color.FgWhite)
	}
}

func BeautifyImports() {
	currentDir := GetCurrentDir()

	err := filepath.Walk(currentDir, func(path string, info fs.FileInfo, err error) error {
		if info.IsDir() {
			return nil
		}

		if strings.HasSuffix(info.Name(), ".go") {
			var content string
			var contentBytes []byte

			contentBytes, err = os.ReadFile(path)
			if err != nil {
				return err
			}

			content = string(contentBytes)
			imports := FindImport(content)
			for _, item := range imports {
				replace := regexp.MustCompile(`\n{1,}`).ReplaceAllString(item, "\n")
				content = strings.ReplaceAll(content, item, replace)
			}

			err = os.WriteFile(path, []byte(content), 0755)
			if err != nil {
				return err
			}
		}

		return nil
	})
	HandleError(err)

	project, err := ProjectName()
	HandleError(err)
	Exec(fmt.Sprintf("goimports -local %s -w .", project), currentDir)
}
